package linmath

import (
	"math"
	"math/rand"

	. "gitee.com/night-tc/gobige/msgdef/protomsg"
)

//////////////////////////////////////////////////////////////////
/* 坐标计算 */

// GetPointInCircle 获取圆心为v，半径为r的圆中的随机点
func GetPointInCircle(v Vector3, r float64) Vector3 {
	// 计算在极坐标系下的角度和距离
	theta := rand.Float64() * 2 * math.Pi
	d := math.Sqrt(rand.Float64()) * r

	// 计算对应的 x、z 分量
	x := d * math.Cos(theta)
	z := d * math.Sin(theta)

	// 组合成随机的点
	return Vector3{
		X: v.X + x,
		Y: v.Y,
		Z: v.Z + z,
	}
}

/*
点pos根据角度rota,平移radius距离
*/
func PosMove(pos Vector3, rota, radius float64) Vector3 {
	forward := AngleToVector(rota)
	forward.MulS(radius)
	return pos.Add(forward)
}

/*
点P0,相对中心点center旋转forward向量所表示的角度
*/
func GetPPosByRadians(center, p0 Vector3, forward Vector3) Vector3 {
	pp0 := p0.Sub(center)

	return Vector3{
		X: pp0.X*forward.Z + pp0.Z*forward.X + center.X,
		Y: center.Y,
		Z: -pp0.X*forward.X + pp0.Z*forward.Z + center.Z,
	}
}

// 根据中心点和长宽和旋转角度，确定新的4个角坐标
func GetSquare4PPos(center Vector3, length, width int, forward Vector3) (p1, p2, p3, p4 Vector3) {
	minX := center.X - float64(length/2)
	minZ := center.Z - float64(width/2)
	maxX := center.X + float64(length/2)
	maxZ := center.Z + float64(width/2)

	pp1 := NewVector3(minX, 0, minZ)
	pp2 := NewVector3(minX, 0, maxZ)
	pp3 := NewVector3(maxX, 0, maxZ)
	pp4 := NewVector3(maxX, 0, minZ)

	p1 = GetPPosByRadians(center, pp1, forward)
	p2 = GetPPosByRadians(center, pp2, forward)
	p3 = GetPPosByRadians(center, pp3, forward)
	p4 = GetPPosByRadians(center, pp4, forward)
	return
}

// 计算两个圆的交点
// 返回值可能有0、1、2个交点(重合时只会有一个)
func IntersectTwoCircles(c1, c2 Vector3, r1, r2 float64) (result1 *Vector3, result2 *Vector3) {
	// 计算两个圆心之间的距离
	d := c1.Sub(c2).Len()

	// 如果两个圆心距离太远没有交点
	if d > r1+r2 {
		return
	}
	// 如果两个圆心距离正好相等是，没有交点
	if d == 0 && r1 == r2 {
		return
	}

	// 如果两个圆心距离太近只有一个交点
	if rl := math.Abs(r1 - r2); d < rl {
		*result1 = NewVector3((c1.X+c2.X)/2, c1.Y, (c1.Z+c2.Z)/2)
		return
	}

	// 计算出交点
	a := (r1*r1 - r2*r2 + d*d) / (2 * d)
	h := math.Sqrt(r1*r1 - a*a)
	x2 := c1.X + a*(c2.X-c1.X)/d
	z2 := c1.Z + a*(c2.Z-c1.Z)/d
	x3_1 := x2 + h*(c2.Z-c1.Z)/d
	z3_1 := z2 - h*(c2.X-c1.X)/d
	x3_2 := x2 - h*(c2.Z-c1.Z)/d
	z3_2 := z2 + h*(c2.X-c1.X)/d

	// 生成包含交点的点向量数组
	if x3_1 == x3_2 && z3_1 == z3_2 {
		*result1 = NewVector3(x3_1, c1.Y, z3_1)
	}
	*result1 = NewVector3(x3_1, c1.Y, z3_1)
	*result2 = NewVector3(x3_2, c1.Y, z3_2)
	return
}

/* 坐标计算 */
//////////////////////////////////////////////////////////////////
/* 角计算 */

// 向量转弧度
func VectorToRadian(v Vector3) float64 {
	return math.Atan2(v.Z, v.X)
}

// VectorToAngle 将一个向量转化成角度，Y轴为垂直轴
// 在使用这个函数的时候，无需对向量归一化
// 向量转角度
func VectorToAngle(v Vector3) float64 {
	return RadianToAngle(VectorToRadian(v))
}

// AngleToVector 将角度转换为方向向量
func AngleToVector(rota float64) Vector3 {
	// 将角度转换为弧度
	radians := rota * math.Pi / 180.0

	// 根据弧度计算方向向量的x和z分量
	x := math.Sin(radians)
	z := math.Cos(radians)

	// 返回Vector3类型的方向向量
	return Vector3{
		X: x,
		Y: 0,
		Z: z,
	}
}

// 角度转弧度
func AngleToRadian(angle float64) float64 {
	return angle * math.Pi / 180.0
}

// 弧度转角度
func RadianToAngle(radian float64) float64 {
	return radian * 180.0 / math.Pi
}

/*
这个函数是用来计算两个角度之间的相对角度差的函数。
WindRelativeAnglesDegrees
给定角度0，角度1（两个角度都是绝对角度数值，角度可以是任意未被归一成±180°），
返回角度0转向角度1之间夹角，值域为 ±180°
应用于怪物转向时候，需要计算角度差。比如：朝向是0°，需要转向到-179°，函数将会计算出-179°，而非181°

Given two angles in degrees, 'wind' the rotation in Angle1 so that it avoids >180 degree flips.
参考网址：
https://docs.unrealengine.com/en-US/API/Runtime/Core/Math/FMath/WindRelativeAnglesDegrees/index.html
https://github.com/suijingfeng/VulkanTutorials/blob/master/Engine/Monkey/Math/Math.cpp

《3D数学基础（第2版）》-第八章-P215
将插值方程中使用的角度之间的差异限制在(-180°,+180°]区间以内找到最短的弧。公式如下：

wrapPi(x)=x-360°floor((x+180°)/360°)
*/
func WindRelativeAnglesDegrees(angle0, angle1 float64) (retAngle float64) {
	diff := angle0 - angle1
	abs := math.Abs(diff)
	retAngle = angle1
	if abs > 180.0 {
		retAngle += 360.0 * GetSign(diff) * math.Floor((abs/360.0)+0.5)
	}
	return
}

// 将度数限制在±180°
func UnwindDegrees(A float64) (R float64) {
	for A > 180.0 {
		A -= 360.0
	}
	for A < -180.0 {
		A += 360.0
	}
	R = A
	return
}

/* 角计算 */
//////////////////////////////////////////////////////////////////
/* 判断计算 */

/*
	计算 |p1 p2| X |p1 p|
	这个函数是计算三维空间内一个点p是否在由p1和p2连成的线段的左侧（相对于p1→p2这个方向）。
	该函数的实现方式是通过计算向量的叉积进行判断，叉积的值可以表示向量间的垂直度和方向关系。

具体来说，如果线段p1p2的方向向量为v，
则点p在向量v的左侧的条件是v和向量p1→p的叉积的z分量大于0。
如果z分量小于0，则p在v的右侧。
如果等于0，则p在v所在的直线上。
*/
func GetCross(p1 Vector3, p2 Vector3, p Vector3) float64 {
	return (p2.X-p1.X)*(p.Z-p1.Z) - (p.X-p1.X)*(p2.Z-p1.Z)
}

// 判断点p是否在p1p2p3p4的正方形内
func IsPointInMatrix(p1 Vector3, p2 Vector3, p3 Vector3, p4 Vector3, p Vector3) bool {
	return GetCross(p1, p2, p)*GetCross(p3, p4, p) >= 0 && GetCross(p2, p3, p)*GetCross(p4, p1, p) >= 0
}

// CheckNavMeshTooFarAway 怪物在寻路到达目标点时，如果寻路路径长度大于与目标的直线距离*2时，视为目标点不可寻路
func CheckNavMeshTooFarAway(srcPos, dstPos Vector3, path []Vector3) bool {
	srcPos.SubS(dstPos)
	srcPos.Y = 0
	checkDst := srcPos.Dot(srcPos) * 2.0

	count := len(path)
	if count < 2 {
		return true
	}

	var tmpPos Vector3
	tmpPos = path[0]
	var totalDst float64
	totalDst = 0.0
	for i := 1; i < count; i++ {
		t := tmpPos.Sub(path[i])
		t.Y = 0
		totalDst += t.Dot(t)
		if totalDst > checkDst {
			return false
		}
		tmpPos = path[i]
	}

	return true
}

///////////////////////////////////////////////////////////////////////////
/* 下面的计算一般都是弧度 */

/*
圆形和扇形是否相交
piepos扇形圆点
r 扇形半径
rota扇形弧度
angle扇形左右的弧度,*2表示扇形完整的弧度

centpos圆的圆心
cr圆的半径
*/
func CheckCirclePieIntersection(piepos Vector3, r float64, rota float64, angle float64, centpos Vector3, cr float64) bool {
	// 计算扇形圆心角的两个端点
	startAngle := rota - angle
	endAngle := rota + angle

	// 计算扇形的外接圆半径
	pieRadius := r / math.Cos(angle)

	// 计算圆心到扇形所扫过区域的两条切线的距离
	distance := (pieRadius * math.Sin(angle)) * 2

	// 判断圆形是否在扇形内
	if centpos.Sub(piepos).Len() <= pieRadius+cr && cr >= distance/2 {
		return true
	}

	// 判断圆心是否在扇形外接圆内
	if centpos.Sub(piepos).Len() < pieRadius && !CheckPieContainsPoint(piepos, r, startAngle, endAngle, centpos) {
		return true
	}

	return false
}

// 判断点是否在扇形内
func CheckPieContainsPoint(piepos Vector3, r float64, startAngle float64, endAngle float64, pos Vector3) bool {
	// 计算点到圆心的距离和角度
	dist := pos.Sub(piepos).Len()
	angle := math.Atan2(pos.Z-piepos.Z, pos.X-piepos.X)

	// 将角度转换到0~360度范围内
	if angle < 0 {
		angle += 2 * math.Pi
	}

	// 判断点是否在扇形内
	if dist <= r && angle >= startAngle && angle <= endAngle {
		return true
	}

	return false
}

/*
判断圆与矩形是否相交
centpos圆心
r半径

p1,p2,p3,p4矩形的4个顶点
*/
func CheckCircleRectIntersection(centpos Vector3, r float64, p1, p2, p3, p4 Vector3) bool {

	// 判断圆心是否在矩形内
	if centpos.X > p1.X && centpos.X < p2.X && centpos.Y > p1.Y && centpos.Y < p4.Y {
		return true
	}

	// 判断矩形四个顶点是否在圆内
	if (p1.Sub(centpos).Len() < r) || (p2.Sub(centpos).Len() < r) || (p3.Sub(centpos).Len() < r) || (p4.Sub(centpos).Len() < r) {
		return true
	}

	// 判断圆心到矩形边缘的距离是否小于等于圆的半径
	if CheckPointToLineDistance(centpos, p1, p2) <= r ||
		CheckPointToLineDistance(centpos, p2, p3) <= r ||
		CheckPointToLineDistance(centpos, p3, p4) <= r ||
		CheckPointToLineDistance(centpos, p4, p1) <= r {
		return true
	}

	return false
}

// 求点到直线的距离
func CheckPointToLineDistance(p Vector3, l1 Vector3, l2 Vector3) float64 {
	a := l2.Z - l1.Z
	b := l1.X - l2.X
	c := l2.X*l1.Z - l1.X*l2.Z
	return math.Abs(a*p.X+b*p.Z+c) / math.Sqrt(a*a+b*b)
}

// 检查某个点是否在背后
// dstPos 原始对象位置
// forwardAngle 原始对象朝向(角度)
// angleOff 正面的夹角半角(角度)
// checkPos 检查者位置
func CheckCalcPosBehind(dstPos, checkPos Vector3, forwardAngle, angleOff int32) bool {
	// 两点重合了，将直接判断为正面
	if dstPos.IsEqualXZ(checkPos) {
		return false
	}
	checkTodst := VectorToAngle(dstPos.Sub(checkPos))
	return math.Abs(float64(checkTodst)-float64(forwardAngle)) < float64(angleOff)
}

/* 判断计算 */
//////////////////////////////////////////////////////////////////
/* 与其他端转化函数 */

// PosClient2Srv 客户端位置转换成服务器位置
// 服务器Z指天，客户端Y指天
func PosClient2Srv(cPos Vector3) (sPos Vector3) {
	sPos.X = cPos.X
	sPos.Y = cPos.Z
	sPos.Z = cPos.Y
	return
}

// PosSrv2Client 服务器位置转换成客户端位置
func PosSrv2Client(sPos Vector3) (cPos Vector3) {
	cPos.X = sPos.X
	cPos.Y = sPos.Z
	cPos.Z = sPos.Y
	return
}

// PosSrv2Nav 服务器位置转换成导航网格位置
func PosSrv2Nav(sPos Vector3) (nPos Vector3) {
	nPos.X = (sPos.X) //* -0.01
	nPos.Y = (sPos.Y) //* 0.01
	nPos.Z = (sPos.Z) //* -0.01
	return
}

// PosSrv2Nav 导航网格位置转换成服务器位置
func PosNav2Srv(nPos Vector3) (sPos Vector3) {
	sPos.X = nPos.X //* -100
	sPos.Y = nPos.Y //* 100
	sPos.Z = nPos.Z //* -100
	return
}

// PosClient2Nav 客户端位置转换成导航网格位置
func PosClient2Nav(cPos *Vector3) (nPos Vector3) {
	nPos.X = cPos.X //* -0.01
	nPos.Y = cPos.Z //* 0.01
	nPos.Z = cPos.Y //* -0.01
	return
}

// PosNav2Client 导航网格位置转换成客户端位置
func PosNav2Client(nPos Vector3) (cPos Vector3) {
	cPos.X = nPos.X //* -100
	cPos.Y = nPos.Z //* -100
	cPos.Z = nPos.Y //* 100
	return
}

// 客户端朝向转服务器朝向（U3D项目）
func ClientRota2Srv(crota int32) (sRota int32) {
	rot := 360 - crota - 270
	if rot > 360 {
		rot = rot - 360
	} else if rot < 0 {
		rot = 360 + rot
	}
	return rot
}

// Client2NavScale 将客户端尺寸转换成导航网格尺寸，用于做动态阻挡尺寸转换
func Client2NavScale(rawSize float32) float32 {
	return rawSize // / 100.0
}

// GetSign 计算符号
func GetSign(diff float64) (sign float64) {
	// abs := math.Abs(diff)
	if diff > 0.0 {
		sign = 1.0
	} else if diff < 0.0 {
		sign = -1.0
	} else {
		sign = 0
	}
	return
}

// 角度进180度的表示
func AngleTo180(angle float64) float64 {
	for angle < 0 && angle >= 180 {
		if angle < 0 {
			angle += 180
		} else if angle >= 180 {
			angle -= 180
		}
	}
	return angle
}
